#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) #pragma warning disable using System; using System.Collections; using System.IO; using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Prng; using BestHTTP.SecureProtocol.Org.BouncyCastle.Security; using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Tls { public abstract class TlsProtocol : TlsCloseable { /* * Our Connection states */ protected const short CS_START = 0; protected const short CS_CLIENT_HELLO = 1; protected const short CS_SERVER_HELLO = 2; protected const short CS_SERVER_SUPPLEMENTAL_DATA = 3; protected const short CS_SERVER_CERTIFICATE = 4; protected const short CS_CERTIFICATE_STATUS = 5; protected const short CS_SERVER_KEY_EXCHANGE = 6; protected const short CS_CERTIFICATE_REQUEST = 7; protected const short CS_SERVER_HELLO_DONE = 8; protected const short CS_CLIENT_SUPPLEMENTAL_DATA = 9; protected const short CS_CLIENT_CERTIFICATE = 10; protected const short CS_CLIENT_KEY_EXCHANGE = 11; protected const short CS_CERTIFICATE_VERIFY = 12; protected const short CS_CLIENT_FINISHED = 13; protected const short CS_SERVER_SESSION_TICKET = 14; protected const short CS_SERVER_FINISHED = 15; protected const short CS_END = 16; /* * Different modes to handle the known IV weakness */ protected const short ADS_MODE_1_Nsub1 = 0; // 1/n-1 record splitting protected const short ADS_MODE_0_N = 1; // 0/n record splitting protected const short ADS_MODE_0_N_FIRSTONLY = 2; // 0/n record splitting on first data fragment only /* * Queues for data from some protocols. */ private ByteQueue mApplicationDataQueue = new ByteQueue(0); private ByteQueue mAlertQueue = new ByteQueue(2); private ByteQueue mHandshakeQueue = new ByteQueue(0); // private ByteQueue mHeartbeatQueue = new ByteQueue(); /* * The Record Stream we use */ internal RecordStream mRecordStream; protected SecureRandom mSecureRandom; private TlsStream mTlsStream = null; private volatile bool mClosed = false; private volatile bool mFailedWithError = false; private volatile bool mAppDataReady = false; private volatile bool mAppDataSplitEnabled = true; private volatile int mAppDataSplitMode = ADS_MODE_1_Nsub1; private byte[] mExpectedVerifyData = null; protected TlsSession mTlsSession = null; protected SessionParameters mSessionParameters = null; protected SecurityParameters mSecurityParameters = null; protected Certificate mPeerCertificate = null; protected int[] mOfferedCipherSuites = null; protected byte[] mOfferedCompressionMethods = null; protected IDictionary mClientExtensions = null; protected IDictionary mServerExtensions = null; protected short mConnectionState = CS_START; protected bool mResumedSession = false; protected bool mReceivedChangeCipherSpec = false; protected bool mSecureRenegotiation = false; protected bool mAllowCertificateStatus = false; protected bool mExpectSessionTicket = false; protected bool mBlocking = true; protected ByteQueueStream mInputBuffers = null; protected ByteQueueStream mOutputBuffer = null; public TlsProtocol(Stream stream, SecureRandom secureRandom) : this(stream, stream, secureRandom) { } public TlsProtocol(Stream input, Stream output, SecureRandom secureRandom) { this.mRecordStream = new RecordStream(this, input, output); this.mSecureRandom = secureRandom; } public TlsProtocol(SecureRandom secureRandom) { this.mBlocking = false; this.mInputBuffers = new ByteQueueStream(); this.mOutputBuffer = new ByteQueueStream(); this.mRecordStream = new RecordStream(this, mInputBuffers, mOutputBuffer); this.mSecureRandom = secureRandom; } protected abstract TlsContext Context { get; } internal abstract AbstractTlsContext ContextAdmin { get; } protected abstract TlsPeer Peer { get; } protected virtual void HandleAlertMessage(byte alertLevel, byte alertDescription) { Peer.NotifyAlertReceived(alertLevel, alertDescription); if (alertLevel == AlertLevel.warning) { HandleAlertWarningMessage(alertDescription); } else { HandleFailure(); throw new TlsFatalAlertReceived(alertDescription); } } protected virtual void HandleAlertWarningMessage(byte alertDescription) { /* * RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own * and close down the connection immediately, discarding any pending writes. */ if (alertDescription == AlertDescription.close_notify) { if (!mAppDataReady) throw new TlsFatalAlert(AlertDescription.handshake_failure); HandleClose(false); } } protected virtual void HandleChangeCipherSpecMessage() { } protected virtual void HandleClose(bool user_canceled) { if (!mClosed) { this.mClosed = true; if (user_canceled && !mAppDataReady) { RaiseAlertWarning(AlertDescription.user_canceled, "User canceled handshake"); } RaiseAlertWarning(AlertDescription.close_notify, "Connection closed"); mRecordStream.SafeClose(); if (!mAppDataReady) { CleanupHandshake(); } } } protected virtual void HandleException(byte alertDescription, string message, Exception cause) { if (!mClosed) { RaiseAlertFatal(alertDescription, message, cause); HandleFailure(); } } protected virtual void HandleFailure() { this.mClosed = true; this.mFailedWithError = true; /* * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated * without proper close_notify messages with level equal to warning. */ // TODO This isn't quite in the right place. Also, as of TLS 1.1 the above is obsolete. InvalidateSession(); mRecordStream.SafeClose(); if (!mAppDataReady) { CleanupHandshake(); } } protected abstract void HandleHandshakeMessage(byte type, MemoryStream buf); protected virtual void ApplyMaxFragmentLengthExtension() { if (mSecurityParameters.maxFragmentLength >= 0) { if (!MaxFragmentLength.IsValid((byte)mSecurityParameters.maxFragmentLength)) throw new TlsFatalAlert(AlertDescription.internal_error); int plainTextLimit = 1 << (8 + mSecurityParameters.maxFragmentLength); mRecordStream.SetPlaintextLimit(plainTextLimit); } } protected virtual void CheckReceivedChangeCipherSpec(bool expected) { if (expected != mReceivedChangeCipherSpec) throw new TlsFatalAlert(AlertDescription.unexpected_message); } protected virtual void CleanupHandshake() { if (this.mExpectedVerifyData != null) { Arrays.Fill(this.mExpectedVerifyData, (byte)0); this.mExpectedVerifyData = null; } this.mSecurityParameters.Clear(); this.mPeerCertificate = null; this.mOfferedCipherSuites = null; this.mOfferedCompressionMethods = null; this.mClientExtensions = null; this.mServerExtensions = null; this.mResumedSession = false; this.mReceivedChangeCipherSpec = false; this.mSecureRenegotiation = false; this.mAllowCertificateStatus = false; this.mExpectSessionTicket = false; } protected virtual void BlockForHandshake() { if (mBlocking) { while (this.mConnectionState != CS_END) { if (this.mClosed) { // NOTE: Any close during the handshake should have raised an exception. throw new TlsFatalAlert(AlertDescription.internal_error); } SafeReadRecord(); } } } protected virtual void CompleteHandshake() { try { this.mConnectionState = CS_END; this.mAlertQueue.Shrink(); this.mHandshakeQueue.Shrink(); this.mRecordStream.FinaliseHandshake(); this.mAppDataSplitEnabled = !TlsUtilities.IsTlsV11(Context); /* * If this was an initial handshake, we are now ready to send and receive application data. */ if (!mAppDataReady) { this.mAppDataReady = true; if (mBlocking) { this.mTlsStream = new TlsStream(this); } } if (this.mTlsSession != null) { if (this.mSessionParameters == null) { this.mSessionParameters = new SessionParameters.Builder() .SetCipherSuite(this.mSecurityParameters.CipherSuite) .SetCompressionAlgorithm(this.mSecurityParameters.CompressionAlgorithm) .SetExtendedMasterSecret(this.mSecurityParameters.IsExtendedMasterSecret) .SetMasterSecret(this.mSecurityParameters.MasterSecret) .SetPeerCertificate(this.mPeerCertificate) .SetPskIdentity(this.mSecurityParameters.PskIdentity) .SetSrpIdentity(this.mSecurityParameters.SrpIdentity) // TODO Consider filtering extensions that aren't relevant to resumed sessions .SetServerExtensions(this.mServerExtensions) .Build(); this.mTlsSession = new TlsSessionImpl(this.mTlsSession.SessionID, this.mSessionParameters); } ContextAdmin.SetResumableSession(this.mTlsSession); } Peer.NotifyHandshakeComplete(); } finally { CleanupHandshake(); } } protected internal void ProcessRecord(byte protocol, byte[] buf, int off, int len) { /* * Have a look at the protocol type, and add it to the correct queue. */ switch (protocol) { case ContentType.alert: { mAlertQueue.AddData(buf, off, len); ProcessAlertQueue(); break; } case ContentType.application_data: { if (!mAppDataReady) throw new TlsFatalAlert(AlertDescription.unexpected_message); mApplicationDataQueue.AddData(buf, off, len); ProcessApplicationDataQueue(); break; } case ContentType.change_cipher_spec: { ProcessChangeCipherSpec(buf, off, len); break; } case ContentType.handshake: { if (mHandshakeQueue.Available > 0) { mHandshakeQueue.AddData(buf, off, len); ProcessHandshakeQueue(mHandshakeQueue); } else { ByteQueue tmpQueue = new ByteQueue(buf, off, len); ProcessHandshakeQueue(tmpQueue); int remaining = tmpQueue.Available; if (remaining > 0) { mHandshakeQueue.AddData(buf, off + len - remaining, remaining); } } break; } //case ContentType.heartbeat: //{ // if (!mAppDataReady) // throw new TlsFatalAlert(AlertDescription.unexpected_message); // // TODO[RFC 6520] // //mHeartbeatQueue.AddData(buf, offset, len); // //ProcessHeartbeat(); // break; //} default: // Record type should already have been checked throw new TlsFatalAlert(AlertDescription.internal_error); } } private void ProcessHandshakeQueue(ByteQueue queue) { while (queue.Available >= 4) { /* * We need the first 4 bytes, they contain type and length of the message. */ byte[] beginning = new byte[4]; queue.Read(beginning, 0, 4, 0); byte type = TlsUtilities.ReadUint8(beginning, 0); int length = TlsUtilities.ReadUint24(beginning, 1); int totalLength = 4 + length; /* * Check if we have enough bytes in the buffer to read the full message. */ if (queue.Available < totalLength) break; /* * RFC 2246 7.4.9. The value handshake_messages includes all handshake messages * starting at client hello up to, but not including, this finished message. * [..] Note: [Also,] Hello Request messages are omitted from handshake hashes. */ if (HandshakeType.hello_request != type) { if (HandshakeType.finished == type) { CheckReceivedChangeCipherSpec(true); TlsContext ctx = Context; if (this.mExpectedVerifyData == null && ctx.SecurityParameters.MasterSecret != null) { this.mExpectedVerifyData = CreateVerifyData(!ctx.IsServer); } } else { CheckReceivedChangeCipherSpec(mConnectionState == CS_END); } queue.CopyTo(mRecordStream.HandshakeHashUpdater, totalLength); } queue.RemoveData(4); MemoryStream buf = queue.ReadFrom(length); /* * Now, parse the message. */ HandleHandshakeMessage(type, buf); } } private void ProcessApplicationDataQueue() { /* * There is nothing we need to do here. * * This function could be used for callbacks when application data arrives in the future. */ } private void ProcessAlertQueue() { while (mAlertQueue.Available >= 2) { /* * An alert is always 2 bytes. Read the alert. */ byte[] alert = mAlertQueue.RemoveData(2, 0); byte alertLevel = alert[0]; byte alertDescription = alert[1]; HandleAlertMessage(alertLevel, alertDescription); } } /** * This method is called, when a change cipher spec message is received. * * @throws IOException If the message has an invalid content or the handshake is not in the correct * state. */ private void ProcessChangeCipherSpec(byte[] buf, int off, int len) { for (int i = 0; i < len; ++i) { byte message = TlsUtilities.ReadUint8(buf, off + i); if (message != ChangeCipherSpec.change_cipher_spec) throw new TlsFatalAlert(AlertDescription.decode_error); if (this.mReceivedChangeCipherSpec || mAlertQueue.Available > 0 || mHandshakeQueue.Available > 0) { throw new TlsFatalAlert(AlertDescription.unexpected_message); } mRecordStream.ReceivedReadCipherSpec(); this.mReceivedChangeCipherSpec = true; HandleChangeCipherSpecMessage(); } } protected internal virtual int ApplicationDataAvailable() { return mApplicationDataQueue.Available; } /** * Read data from the network. The method will return immediately, if there is still some data * left in the buffer, or block until some application data has been read from the network. * * @param buf The buffer where the data will be copied to. * @param offset The position where the data will be placed in the buffer. * @param len The maximum number of bytes to read. * @return The number of bytes read. * @throws IOException If something goes wrong during reading data. */ protected internal virtual int ReadApplicationData(byte[] buf, int offset, int len) { if (len < 1) return 0; while (mApplicationDataQueue.Available == 0) { if (this.mClosed) { if (this.mFailedWithError) throw new IOException("Cannot read application data on failed TLS connection"); if (!mAppDataReady) throw new InvalidOperationException("Cannot read application data until initial handshake completed."); return 0; } SafeReadRecord(); } len = System.Math.Min(len, mApplicationDataQueue.Available); mApplicationDataQueue.RemoveData(buf, offset, len, 0); return len; } protected virtual void SafeCheckRecordHeader(byte[] recordHeader) { try { mRecordStream.CheckRecordHeader(recordHeader); } catch (TlsFatalAlert e) { HandleException(e.AlertDescription, "Failed to read record", e); throw e; } catch (IOException e) { HandleException(AlertDescription.internal_error, "Failed to read record", e); throw e; } catch (Exception e) { HandleException(AlertDescription.internal_error, "Failed to read record", e); throw new TlsFatalAlert(AlertDescription.internal_error, e); } } protected virtual void SafeReadRecord() { try { if (mRecordStream.ReadRecord()) return; if (!mAppDataReady) throw new TlsFatalAlert(AlertDescription.handshake_failure); } catch (TlsFatalAlertReceived e) { // Connection failure already handled at source throw e; } catch (TlsFatalAlert e) { HandleException(e.AlertDescription, "Failed to read record", e); throw e; } catch (IOException e) { HandleException(AlertDescription.internal_error, "Failed to read record", e); throw e; } catch (Exception e) { HandleException(AlertDescription.internal_error, "Failed to read record", e); throw new TlsFatalAlert(AlertDescription.internal_error, e); } HandleFailure(); throw new TlsNoCloseNotifyException(); } protected virtual void SafeWriteRecord(byte type, byte[] buf, int offset, int len) { try { mRecordStream.WriteRecord(type, buf, offset, len); } catch (TlsFatalAlert e) { HandleException(e.AlertDescription, "Failed to write record", e); throw e; } catch (IOException e) { HandleException(AlertDescription.internal_error, "Failed to write record", e); throw e; } catch (Exception e) { HandleException(AlertDescription.internal_error, "Failed to write record", e); throw new TlsFatalAlert(AlertDescription.internal_error, e); } } /** * Send some application data to the remote system. * <p/> * The method will handle fragmentation internally. * * @param buf The buffer with the data. * @param offset The position in the buffer where the data is placed. * @param len The length of the data. * @throws IOException If something goes wrong during sending. */ protected internal virtual void WriteData(byte[] buf, int offset, int len) { if (this.mClosed) throw new IOException("Cannot write application data on closed/failed TLS connection"); while (len > 0) { /* * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are * potentially useful as a traffic analysis countermeasure. * * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting. */ if (this.mAppDataSplitEnabled) { /* * Protect against known IV attack! * * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE. */ switch (mAppDataSplitMode) { case ADS_MODE_0_N: SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0); break; case ADS_MODE_0_N_FIRSTONLY: this.mAppDataSplitEnabled = false; SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0); break; case ADS_MODE_1_Nsub1: default: SafeWriteRecord(ContentType.application_data, buf, offset, 1); ++offset; --len; break; } } if (len > 0) { // Fragment data according to the current fragment limit. int toWrite = System.Math.Min(len, mRecordStream.GetPlaintextLimit()); SafeWriteRecord(ContentType.application_data, buf, offset, toWrite); offset += toWrite; len -= toWrite; } } } protected virtual void SetAppDataSplitMode(int appDataSplitMode) { if (appDataSplitMode < ADS_MODE_1_Nsub1 || appDataSplitMode > ADS_MODE_0_N_FIRSTONLY) throw new ArgumentException("Illegal appDataSplitMode mode: " + appDataSplitMode, "appDataSplitMode"); this.mAppDataSplitMode = appDataSplitMode; } protected virtual void WriteHandshakeMessage(byte[] buf, int off, int len) { if (len < 4) throw new TlsFatalAlert(AlertDescription.internal_error); byte type = TlsUtilities.ReadUint8(buf, off); if (type != HandshakeType.hello_request) { mRecordStream.HandshakeHashUpdater.Write(buf, off, len); } int total = 0; do { // Fragment data according to the current fragment limit. int toWrite = System.Math.Min(len - total, mRecordStream.GetPlaintextLimit()); SafeWriteRecord(ContentType.handshake, buf, off + total, toWrite); total += toWrite; } while (total < len); } /// <summary>The secure bidirectional stream for this connection</summary> /// <remarks>Only allowed in blocking mode.</remarks> public virtual Stream Stream { get { if (!mBlocking) throw new InvalidOperationException("Cannot use Stream in non-blocking mode! Use OfferInput()/OfferOutput() instead."); return this.mTlsStream; } } /** * Should be called in non-blocking mode when the input data reaches EOF. */ public virtual void CloseInput() { if (mBlocking) throw new InvalidOperationException("Cannot use CloseInput() in blocking mode!"); if (mClosed) return; if (mInputBuffers.Available > 0) throw new EndOfStreamException(); if (!mAppDataReady) throw new TlsFatalAlert(AlertDescription.handshake_failure); throw new TlsNoCloseNotifyException(); } /** * Equivalent to <code>OfferInput(input, 0, input.length)</code> * @see TlsProtocol#OfferInput(byte[], int, int) * @param input The input buffer to offer * @throws IOException If an error occurs while decrypting or processing a record */ public virtual void OfferInput(byte[] input) { OfferInput(input, 0, input.Length); } /** * Offer input from an arbitrary source. Only allowed in non-blocking mode.<br/> * <br/> * This method will decrypt and process all records that are fully available. * If only part of a record is available, the buffer will be retained until the * remainder of the record is offered.<br/> * <br/> * If any records containing application data were processed, the decrypted data * can be obtained using {@link #readInput(byte[], int, int)}. If any records * containing protocol data were processed, a response may have been generated. * You should always check to see if there is any available output after calling * this method by calling {@link #getAvailableOutputBytes()}. * @param input The input buffer to offer * @param inputOff The offset within the input buffer that input begins * @param inputLen The number of bytes of input being offered * @throws IOException If an error occurs while decrypting or processing a record */ public virtual void OfferInput(byte[] input, int inputOff, int inputLen) { if (mBlocking) throw new InvalidOperationException("Cannot use OfferInput() in blocking mode! Use Stream instead."); if (mClosed) throw new IOException("Connection is closed, cannot accept any more input"); mInputBuffers.Write(input, inputOff, inputLen); // loop while there are enough bytes to read the length of the next record while (mInputBuffers.Available >= RecordStream.TLS_HEADER_SIZE) { byte[] recordHeader = new byte[RecordStream.TLS_HEADER_SIZE]; mInputBuffers.Peek(recordHeader); int totalLength = TlsUtilities.ReadUint16(recordHeader, RecordStream.TLS_HEADER_LENGTH_OFFSET) + RecordStream.TLS_HEADER_SIZE; if (mInputBuffers.Available < totalLength) { // not enough bytes to read a whole record SafeCheckRecordHeader(recordHeader); break; } SafeReadRecord(); if (mClosed) { if (mConnectionState != CS_END) { // NOTE: Any close during the handshake should have raised an exception. throw new TlsFatalAlert(AlertDescription.internal_error); } break; } } } /** * Gets the amount of received application data. A call to {@link #readInput(byte[], int, int)} * is guaranteed to be able to return at least this much data.<br/> * <br/> * Only allowed in non-blocking mode. * @return The number of bytes of available application data */ public virtual int GetAvailableInputBytes() { if (mBlocking) throw new InvalidOperationException("Cannot use GetAvailableInputBytes() in blocking mode! Use ApplicationDataAvailable() instead."); return ApplicationDataAvailable(); } /** * Retrieves received application data. Use {@link #getAvailableInputBytes()} to check * how much application data is currently available. This method functions similarly to * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data * is available, nothing will be copied and zero will be returned.<br/> * <br/> * Only allowed in non-blocking mode. * @param buffer The buffer to hold the application data * @param offset The start offset in the buffer at which the data is written * @param length The maximum number of bytes to read * @return The total number of bytes copied to the buffer. May be less than the * length specified if the length was greater than the amount of available data. */ public virtual int ReadInput(byte[] buffer, int offset, int length) { if (mBlocking) throw new InvalidOperationException("Cannot use ReadInput() in blocking mode! Use Stream instead."); return ReadApplicationData(buffer, offset, System.Math.Min(length, ApplicationDataAvailable())); } /** * Offer output from an arbitrary source. Only allowed in non-blocking mode.<br/> * <br/> * After this method returns, the specified section of the buffer will have been * processed. Use {@link #readOutput(byte[], int, int)} to get the bytes to * transmit to the other peer.<br/> * <br/> * This method must not be called until after the handshake is complete! Attempting * to call it before the handshake is complete will result in an exception. * @param buffer The buffer containing application data to encrypt * @param offset The offset at which to begin reading data * @param length The number of bytes of data to read * @throws IOException If an error occurs encrypting the data, or the handshake is not complete */ public virtual void OfferOutput(byte[] buffer, int offset, int length) { if (mBlocking) throw new InvalidOperationException("Cannot use OfferOutput() in blocking mode! Use Stream instead."); if (!mAppDataReady) throw new IOException("Application data cannot be sent until the handshake is complete!"); WriteData(buffer, offset, length); } /** * Gets the amount of encrypted data available to be sent. A call to * {@link #readOutput(byte[], int, int)} is guaranteed to be able to return at * least this much data.<br/> * <br/> * Only allowed in non-blocking mode. * @return The number of bytes of available encrypted data */ public virtual int GetAvailableOutputBytes() { if (mBlocking) throw new InvalidOperationException("Cannot use GetAvailableOutputBytes() in blocking mode! Use Stream instead."); return mOutputBuffer.Available; } /** * Retrieves encrypted data to be sent. Use {@link #getAvailableOutputBytes()} to check * how much encrypted data is currently available. This method functions similarly to * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data * is available, nothing will be copied and zero will be returned.<br/> * <br/> * Only allowed in non-blocking mode. * @param buffer The buffer to hold the encrypted data * @param offset The start offset in the buffer at which the data is written * @param length The maximum number of bytes to read * @return The total number of bytes copied to the buffer. May be less than the * length specified if the length was greater than the amount of available data. */ public virtual int ReadOutput(byte[] buffer, int offset, int length) { if (mBlocking) throw new InvalidOperationException("Cannot use ReadOutput() in blocking mode! Use Stream instead."); return mOutputBuffer.Read(buffer, offset, length); } protected virtual void InvalidateSession() { if (this.mSessionParameters != null) { this.mSessionParameters.Clear(); this.mSessionParameters = null; } if (this.mTlsSession != null) { this.mTlsSession.Invalidate(); this.mTlsSession = null; } } protected virtual void ProcessFinishedMessage(MemoryStream buf) { if (mExpectedVerifyData == null) throw new TlsFatalAlert(AlertDescription.internal_error); byte[] verify_data = TlsUtilities.ReadFully(mExpectedVerifyData.Length, buf); AssertEmpty(buf); /* * Compare both checksums. */ if (!Arrays.ConstantTimeAreEqual(mExpectedVerifyData, verify_data)) { /* * Wrong checksum in the finished message. */ throw new TlsFatalAlert(AlertDescription.decrypt_error); } } protected virtual void RaiseAlertFatal(byte alertDescription, string message, Exception cause) { Peer.NotifyAlertRaised(AlertLevel.fatal, alertDescription, message, cause); byte[] alert = new byte[]{ AlertLevel.fatal, alertDescription }; try { mRecordStream.WriteRecord(ContentType.alert, alert, 0, 2); } catch (Exception) { // We are already processing an exception, so just ignore this } } protected virtual void RaiseAlertWarning(byte alertDescription, string message) { Peer.NotifyAlertRaised(AlertLevel.warning, alertDescription, message, null); byte[] alert = new byte[]{ AlertLevel.warning, alertDescription }; SafeWriteRecord(ContentType.alert, alert, 0, 2); } protected virtual void SendCertificateMessage(Certificate certificate) { if (certificate == null) { certificate = Certificate.EmptyChain; } if (certificate.IsEmpty) { TlsContext context = Context; if (!context.IsServer) { ProtocolVersion serverVersion = Context.ServerVersion; if (serverVersion.IsSsl) { string errorMessage = serverVersion.ToString() + " client didn't provide credentials"; RaiseAlertWarning(AlertDescription.no_certificate, errorMessage); return; } } } HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate); certificate.Encode(message); message.WriteToRecordStream(this); } protected virtual void SendChangeCipherSpecMessage() { byte[] message = new byte[]{ 1 }; SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length); mRecordStream.SentWriteCipherSpec(); } protected virtual void SendFinishedMessage() { byte[] verify_data = CreateVerifyData(Context.IsServer); HandshakeMessage message = new HandshakeMessage(HandshakeType.finished, verify_data.Length); message.Write(verify_data, 0, verify_data.Length); message.WriteToRecordStream(this); } protected virtual void SendSupplementalDataMessage(IList supplementalData) { HandshakeMessage message = new HandshakeMessage(HandshakeType.supplemental_data); WriteSupplementalData(message, supplementalData); message.WriteToRecordStream(this); } protected virtual byte[] CreateVerifyData(bool isServer) { TlsContext context = Context; string asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished; byte[] sslSender = isServer ? TlsUtilities.SSL_SERVER : TlsUtilities.SSL_CLIENT; byte[] hash = GetCurrentPrfHash(context, mRecordStream.HandshakeHash, sslSender); return TlsUtilities.CalculateVerifyData(context, asciiLabel, hash); } /** * Closes this connection. * * @throws IOException If something goes wrong during closing. */ public virtual void Close() { HandleClose(true); } protected internal virtual void Flush() { mRecordStream.Flush(); } public virtual bool IsClosed { get { return mClosed; } } protected virtual short ProcessMaxFragmentLengthExtension(IDictionary clientExtensions, IDictionary serverExtensions, byte alertDescription) { short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions); if (maxFragmentLength >= 0) { if (!MaxFragmentLength.IsValid((byte)maxFragmentLength) || (!this.mResumedSession && maxFragmentLength != TlsExtensionsUtilities .GetMaxFragmentLengthExtension(clientExtensions))) { throw new TlsFatalAlert(alertDescription); } } return maxFragmentLength; } protected virtual void RefuseRenegotiation() { /* * RFC 5746 4.5 SSLv3 clients that refuse renegotiation SHOULD use a fatal * handshake_failure alert. */ if (TlsUtilities.IsSsl(Context)) throw new TlsFatalAlert(AlertDescription.handshake_failure); RaiseAlertWarning(AlertDescription.no_renegotiation, "Renegotiation not supported"); } /** * Make sure the InputStream 'buf' now empty. Fail otherwise. * * @param buf The InputStream to check. * @throws IOException If 'buf' is not empty. */ protected internal static void AssertEmpty(MemoryStream buf) { if (buf.Position < buf.Length) throw new TlsFatalAlert(AlertDescription.decode_error); } protected internal static byte[] CreateRandomBlock(bool useGmtUnixTime, IRandomGenerator randomGenerator) { byte[] result = new byte[32]; randomGenerator.NextBytes(result); if (useGmtUnixTime) { TlsUtilities.WriteGmtUnixTime(result, 0); } return result; } protected internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection) { return TlsUtilities.EncodeOpaque8(renegotiated_connection); } protected internal static void EstablishMasterSecret(TlsContext context, TlsKeyExchange keyExchange) { byte[] pre_master_secret = keyExchange.GeneratePremasterSecret(); try { context.SecurityParameters.masterSecret = TlsUtilities.CalculateMasterSecret(context, pre_master_secret); } finally { // TODO Is there a way to ensure the data is really overwritten? /* * RFC 2246 8.1. The pre_master_secret should be deleted from memory once the * master_secret has been computed. */ if (pre_master_secret != null) { Arrays.Fill(pre_master_secret, (byte)0); } } } /** * 'sender' only relevant to SSLv3 */ protected internal static byte[] GetCurrentPrfHash(TlsContext context, TlsHandshakeHash handshakeHash, byte[] sslSender) { IDigest d = handshakeHash.ForkPrfHash(); if (sslSender != null && TlsUtilities.IsSsl(context)) { d.BlockUpdate(sslSender, 0, sslSender.Length); } return DigestUtilities.DoFinal(d); } protected internal static IDictionary ReadExtensions(MemoryStream input) { if (input.Position >= input.Length) return null; byte[] extBytes = TlsUtilities.ReadOpaque16(input); AssertEmpty(input); MemoryStream buf = new MemoryStream(extBytes, false); // Integer -> byte[] IDictionary extensions = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateHashtable(); while (buf.Position < buf.Length) { int extension_type = TlsUtilities.ReadUint16(buf); byte[] extension_data = TlsUtilities.ReadOpaque16(buf); /* * RFC 3546 2.3 There MUST NOT be more than one extension of the same type. */ if (extensions.Contains(extension_type)) throw new TlsFatalAlert(AlertDescription.illegal_parameter); extensions.Add(extension_type, extension_data); } return extensions; } protected internal static IList ReadSupplementalDataMessage(MemoryStream input) { byte[] supp_data = TlsUtilities.ReadOpaque24(input); AssertEmpty(input); MemoryStream buf = new MemoryStream(supp_data, false); IList supplementalData = BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.CreateArrayList(); while (buf.Position < buf.Length) { int supp_data_type = TlsUtilities.ReadUint16(buf); byte[] data = TlsUtilities.ReadOpaque16(buf); supplementalData.Add(new SupplementalDataEntry(supp_data_type, data)); } return supplementalData; } protected internal static void WriteExtensions(Stream output, IDictionary extensions) { MemoryStream buf = new MemoryStream(); /* * NOTE: There are reports of servers that don't accept a zero-length extension as the last * one, so we write out any zero-length ones first as a best-effort workaround. */ WriteSelectedExtensions(buf, extensions, true); WriteSelectedExtensions(buf, extensions, false); byte[] extBytes = buf.ToArray(); TlsUtilities.WriteOpaque16(extBytes, output); } protected internal static void WriteSelectedExtensions(Stream output, IDictionary extensions, bool selectEmpty) { foreach (int extension_type in extensions.Keys) { byte[] extension_data = (byte[])extensions[extension_type]; if (selectEmpty == (extension_data.Length == 0)) { TlsUtilities.CheckUint16(extension_type); TlsUtilities.WriteUint16(extension_type, output); TlsUtilities.WriteOpaque16(extension_data, output); } } } protected internal static void WriteSupplementalData(Stream output, IList supplementalData) { MemoryStream buf = new MemoryStream(); foreach (SupplementalDataEntry entry in supplementalData) { int supp_data_type = entry.DataType; TlsUtilities.CheckUint16(supp_data_type); TlsUtilities.WriteUint16(supp_data_type, buf); TlsUtilities.WriteOpaque16(entry.Data, buf); } byte[] supp_data = buf.ToArray(); TlsUtilities.WriteOpaque24(supp_data, output); } protected internal static int GetPrfAlgorithm(TlsContext context, int ciphersuite) { bool isTLSv12 = TlsUtilities.IsTlsV12(context); switch (ciphersuite) { case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256: case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256: case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256: case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256: case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256: case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256: case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM: case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_128_OCB: case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM: case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_AES_256_OCB: case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_128_OCB: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8: case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_AES_256_OCB: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256: case CipherSuite.DRAFT_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256: case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_128_OCB: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_AES_256_OCB: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_128_OCB: case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_AES_256_OCB: case CipherSuite.DRAFT_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_128_OCB: case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_AES_256_OCB: case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8: case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8: case CipherSuite.TLS_PSK_WITH_AES_128_CCM: case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8: case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_PSK_WITH_AES_128_OCB: case CipherSuite.TLS_PSK_WITH_AES_256_CCM: case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8: case CipherSuite.DRAFT_TLS_PSK_WITH_AES_256_OCB: case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256: case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.DRAFT_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256: case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256: case CipherSuite.TLS_RSA_WITH_AES_128_CCM: case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8: case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256: case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256: case CipherSuite.TLS_RSA_WITH_AES_256_CCM: case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8: case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256: case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256: case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256: case CipherSuite.TLS_RSA_WITH_NULL_SHA256: { if (isTLSv12) { return PrfAlgorithm.tls_prf_sha256; } throw new TlsFatalAlert(AlertDescription.illegal_parameter); } case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384: case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384: case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384: { if (isTLSv12) { return PrfAlgorithm.tls_prf_sha384; } throw new TlsFatalAlert(AlertDescription.illegal_parameter); } case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384: case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384: case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_PSK_WITH_NULL_SHA384: case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384: case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384: { if (isTLSv12) { return PrfAlgorithm.tls_prf_sha384; } return PrfAlgorithm.tls_prf_legacy; } default: { if (isTLSv12) { return PrfAlgorithm.tls_prf_sha256; } return PrfAlgorithm.tls_prf_legacy; } } } internal class HandshakeMessage : MemoryStream { internal HandshakeMessage(byte handshakeType) : this(handshakeType, 60) { } internal HandshakeMessage(byte handshakeType, int length) : base(length + 4) { TlsUtilities.WriteUint8(handshakeType, this); // Reserve space for length TlsUtilities.WriteUint24(0, this); } internal void Write(byte[] data) { Write(data, 0, data.Length); } internal void WriteToRecordStream(TlsProtocol protocol) { // Patch actual length back in long length = Length - 4; TlsUtilities.CheckUint24(length); this.Position = 1; TlsUtilities.WriteUint24((int)length, this); #if PORTABLE || NETFX_CORE byte[] buf = ToArray(); int bufLen = buf.Length; #else byte[] buf = GetBuffer(); int bufLen = (int)Length; #endif protocol.WriteHandshakeMessage(buf, 0, bufLen); BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Platform.Dispose(this); } } } } #pragma warning restore #endif